ArrayList objects;

interface Drawable{
  public void draw();
}

interface Mobile{
  public void move();
  public void collide(ArrayList others);
}
   
 
class Ball implements Drawable, Mobile{
  PVector c;
  PVector v;
  PVector a;
  float d;
 
  float t0;
 
  public Ball(PVector center, PVector velocity, PVector acceleration, float diameter){
    this.c = center;
    this.d = diameter;
    this.v = velocity;
    this.a = acceleration;   
    t0 = millis();
  }
 
 
  public void draw(){
    stroke(255);
    fill(255);
    ellipse(c.x, c.y, d, d);
  }

  public void move(){
    float t;
   
    t = millis();
    
    v.add(PVector.mult(a,(t-t0)/1000));
    c.add(PVector.mult(v,(t-t0)/1000));
    
    t0 = t;
  }
 
  public void collide(ArrayList others){
    Mobile m;
    
   for(int i = 0; i < others.size(); i++){
      m = (Mobile)others.get(i);
      if(m instanceof Ball){
        collideWithBall((Ball) m);
      }// if is a ball
      else
      if(m instanceof Line){
        collideWithLine((Line) m);
      } // if is a line
      

    } // for all other stuff
    
  } // collide
 
  public void collideWithLine(Line l){
      // Verificar que pueda haber colision (que la bola este lo suficientemente cerca en x o en y de cualquiera de los extremos)
      
      // Hallar el punto de la linea mas cercano al centro de la bola
      // Verificar que el punto hallado esté dentro del segmento
      // Hallar el vector entre el punto hallado y el centro de la bola, normalizarlo y muliplicarlo por el modulo de la velocidad
      
      // El segmento va desde el punto a hasta el b
      // c es el centro de la bola
      // p es el punto mas cercano del segmento a c
      // r es el porcentaje de la longitud del segmento en que se ubica p
      //
      //                    *c
      //                    |
      // a *----------------|-* b
      //                    p
      //   /________________/  
      //                    r * |ab|
      //
      //       AC . AB
      //  r = ---------  
      //       ||AB||^2
      //  r=0      P = A
      //  r=1      P = B
      //  r<0      P is on the backward extension of AB
      //  r>1      P is on the forward extension of AB
      //  0<r<1    P is interior to AB
      //
      // a = l.p1
      // b = l.p2
      // c = this.c

      PVector ac;
      PVector ab;
      PVector ap;
      PVector pc;
      PVector p;
      PVector aux;
      float r;
      // Valor original de la magnitud de la velocidad
      float oldS = this.v.mag();      
      
      ac = PVector.sub(c, l.point1);
      ab = PVector.sub(l.point2, l.point1);
      
      if(ab.mag() == 0) 
        r = 0;
      else
        r = ac.dot(ab)/(sq(ab.mag()));
      
      // Si el centro de la bola no cae sobre el segmento
      if(r < 0 && r >= -d/2)
        r = 0;
      
      if(r > 1 && r <= 1 +d/2)
        r = 1;  
        
      if(r >= 0 && r <= 1){
          
        // r * longitud == longitud del segmento ap
        // ab normalizado da la direccion da ap
        // Multiplicando por la longitud, se obtiene el segmento ap  
        aux = ab.get();
        aux.normalize();
        
        ab = PVector.sub(l.point2, l.point1);
        p = PVector.add(l.point1, PVector.mult(ab,r));
        
       /* stroke(0,0,255);
        ellipse(p.x, p.y, 5, 5);
        stroke(0); */
        //println("p = " +  p);
        // El punto p es el segundo componente del vector que representa al segmento a
              
        // Verificar si efectivamente hay colision
        //println(PVector.sub(p,c).mag() + " -- " + this.d/2);
        if(PVector.sub(p,c).mag() <= this.d/2){
          
          // El vector pc da la direccion de la nueva velocidad
          pc = PVector.sub(c,p);
          pc.normalize();
          pc.mult(v.mag());
          
          v.add(pc);
          v.normalize();
          v.mult(oldS*friction);
        } // if dist(p,c) < radio
      } // if 0<r<1
  } // collideWithLine
    
 
  public void collideWithBall(Ball b){
   // Modulo de la velocidad antes de la colision
    float oldS = this.v.mag();  
    float dist; 
    PVector aux;
    
    if(b!= this && PVector.sub(b.c , c).mag() <= d){
          // La velocidad sera vector que vaya desde el centro de la otra bola hasta el centro de esta, con el mismo modulo que tenia antes de la colision
          this.v = PVector.sub(c, b.c);
          v.normalize();
          v.mult(oldS*friction);
          
          
          // Eliminar superposicion
          // HMMMMMMMMMMM NO DEBERIA HACER FALTA
          dist = PVector.sub(b.c, c).mag();
          dist -= 0.001;
          if(false && dist<d){
            //println("dist = " + dist);
           // dist -= 0.001;
            aux = PVector.sub(b.c,c);
            aux.normalize();
            aux.mult(d-dist);
            
            //stroke(0,255,0);
            //fill(0,255,0);
            //ellipse(c.x, c.y, c.x - aux.x, c.y - aux.y, 10,10);
            //ellipse(c.x - aux.x, c.y - aux.y, 10,10);
            
            stroke(255);
            fill(255);
            
            //println("d - dist = " + (d - dist));
            c.sub(aux);
          } // if overlays
     } // if colisiona
  } // collideWithBall
} // BALL

class Line implements Drawable, Mobile{
  PVector point1;
  PVector point2;
 
  public Line(PVector p1, PVector p2){
    this.point1 = p1;
    this.point2 = p2;
  }
 
  public void draw(){
    stroke(255);
    fill(255);
    line(point1.x, point1.y, point2.x, point2.y);
  }
 
  public void move(){
  }
  public void collide(ArrayList others){
  }
} // LINE


// ----------------------------
// ----------------------------

PVector point1 = null;
PVector point2 = null;

boolean pressed = false;

float g = 40;
float friction = 0.8;

// ----------------------------
// ----------------------------

void setup(){
  objects = new ArrayList();
  size(700, 700);
  background(120,120,0);
  stroke(255);
} // SETUP


void draw(){
  background(0);
 
  // Draw everything
  for(int i = 0; i < objects.size(); i++){
    ((Mobile)objects.get(i)).collide(objects);
  }

  for(int i = 0; i < objects.size(); i++){
    ((Mobile)objects.get(i)).move();
    ((Drawable)objects.get(i)).draw();
  }
 
  if(pressed){
    line(point1.x, point1.y, mouseX, mouseY);
  }
  
  cleanup(objects);  
} // DRAW


void cleanup(ArrayList objects){
  Ball b;
  Mobile m;
  ArrayList tempList = (ArrayList)objects.clone();
  
  for(Iterator it = tempList.iterator(); it.hasNext();){
    m = (Mobile) it.next();
    if(m instanceof Ball){
      b = (Ball) m;
      if(b.c.x < - height || b.c.x > 2*height || b.c.y < -width || b.c.y > 2 * width)
          objects.remove(b); 
    } // if it´s a ball
  } // for all objects in the temporary copy of the object list
} // cleanup

void mouseReleased(){
    if(point1 != null){
      point2 = new PVector(mouseX, mouseY);
     
      if(PVector.sub(point1, point2).mag() > 2){
        objects.add(new Line(point1, point2));
        point1 = null;
        point2 = null;
      }else{
        objects.add(new Ball(point1, new PVector(0,0),new PVector(0,g), 20));
      }
    }
   
    pressed = false;
} // MOUSE RELEASED

void mousePressed(){
      point1 = new PVector(mouseX, mouseY);
      pressed = true;
} // MOUSE PRESSED

